#region File Header
//
// FiniteQueue.cs - A generic finite queue (ring/circular buffer) with index [] operator. 
//
// Copyright (C) Javier Valcarce. BSD License
#endregion

#region Using Statements
using System;
using System.Threading;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
#endregion

namespace Arq
{
        /// <summary>
        /// FinitQueueBA (BA = Blocking API)
        /// This class implements a thread-safe generic finite queue (also called circular buffer, 
        /// ring buffer or bounded buffer) with *blocking* Enqueue()/Dequeue() operations. It has 
        /// also a index [] operator to examine and change the data inside the queue. 
        /// </summary>
        public class FiniteQueueBA<T> : IFiniteQueue<T>
        {
            #region Private Members
            int    head;
            int    tail;            
            T[]    buff;
            int    count;
            int    space;
            object sync = new object();
            #endregion

            #region Constructor
            /// <summary>
            /// Creates a ring buffer of n elements of type T.
            /// </summary>
            /// <param name="n">Queue's capacity</param>
            public FiniteQueueBA(int n)
            {
                if (n < 1)
                    throw new ArgumentException();

                buff = new T[n];
                Clear();
            }

            /// <summary>
            /// Creates a ring buffer using a T array as the underlaying storage. 
            /// </summary>
            /// <param name="v">Array of T elements</param>
            public FiniteQueueBA(T[] v)
            {
                if (v.Length < 1)
                    throw new ArgumentException();

                buff = v;
                Clear();
            }
            #endregion

            #region Public Methods
            /// <summary>
            /// Removes all objects from the FiniteQueue
            /// </summary>
            public void Clear()
            {
                lock (sync)
                {
                    head  = 0;
                    tail  = 0;
                    count = 0;
                    space = buff.Length;
                }
            }
            
            /// <summary>
            /// Enqueue a single object to the end of the FiniteQueue
            /// </summary>
            /// <param name="item"></param>
            public int Enqueue(T token)
            {
                lock (sync)
                {
                    if (space == 0)
                        Monitor.Wait(sync);

                    buff[tail] = token;
                    tail++;
                    if (tail == buff.Length)
                        tail = 0;

                    count++;
                    space--;

                    Monitor.Pulse(sync);
                    return 1;
                }
            }

            /// <summary>
            /// Enqueue an array of objects to the end of the RingBuffer<![CDATA[<T>]]>
            /// </summary>
            /// <param name="item"></param>
            public int Enqueue(T[] src, int offset, int count)
            {
                lock (sync)
                {
                    if (this.space < count)
                        Monitor.Wait(sync);

                    for (int i = 0; i < count; i++)
                    {
                        buff[tail] = src[offset + i];
                        tail++;
                        if (tail == buff.Length)
                            tail = 0;
                    }

                    this.count += count;
                    this.space -= count;

                    Monitor.Pulse(sync);
                    return count;
                }
            }

            /// <summary>
            /// Removes and returns the token at the beginning of the FiniteQueue
            /// </summary>
            /// <returns>The number of tokens dequeued, always 1</returns>
            public int Dequeue(out T token)
            {
                lock (sync)
                {
                    if (count == 0)
                        Monitor.Wait(sync);

                    token = buff[head];
                    head++;
                    if (head == buff.Length)
                        head = 0;

                    count--;
                    space++;

                    Monitor.Pulse(sync);
                    return 1;
                }
            }

            /// <summary>
            /// Removes and returns the first count tokens at the beginning of the FiniteQueue
            /// </summary>
            /// <param name="dst">Destination array to store the dequeued tokens</param>
            /// <param name="offset">Offset in the dst array</param>
            /// <param name="count">Number of tokens to be dequeued</param>
            /// <returns></returns>
            public int Dequeue(T[] dst, int offset, int count)
            {
                lock (sync)
                {
                    if (this.count < count)
                        Monitor.Wait(sync);

                    for (int i = 0; i < count; i++)
                    {
                        dst[offset + i] = buff[head];
                        head++;
                        if (head == buff.Length)
                            head = 0;
                    }

                    this.count -= count;
                    this.space += count;

                    Monitor.Pulse(sync);
                    return count;
                }
            }
            #endregion

            #region Properties
            /// <summary>
            /// Index operator.
            /// Warning: this operator use MOD (%) to compute the index, its performance is not 
            /// very good. Use it sporadically.
            /// </summary>
            /// <param name="index">Index in the queue</param>
            /// <returns>The indexed element</returns>
            public T this[int index]
            {
                get 
                {
                    lock (sync)
                    {
                        return buff[(head + index) % buff.Length];
                    }
                } 
                
                set 
                {
                    lock (sync)
                    {
                        buff[(head + index) % buff.Length] = value;
                    }
                } 
            }

            public int Capacity
            {
                get
                {
                    return buff.Length;
                }
            }         

            /// <summary>
            /// Number of tokens in the queue
            /// </summary>
            public int Count
            {
                get
                {
                    lock (sync)
                    {
                        return count;
                    }
                }
            }

            /// <summary>
            /// Available free space in the queue
            /// </summary>
            public int Space
            {
                get
                {
                    lock (sync)
                    {
                        return space;
                    }
                }
            }
            #endregion
        }
}
